#!/usr/bin/env bash

include "arguments.sh"
include "test.sh"

_OBJECTV2_struct_name=""
_OBJECTV2_cons_eval=""
_OBJECTV2_ctor_eval=""
_OBJECTV2_dtor_eval=""
_OBJECTV2_dest_eval=""

function objectv2::_option::cargs()
{
    local ctor_id="0"
    local valsname="$(arguments::get_val)"
    for valname in $(sed "s@,@ @g" <<< "$valsname"); do
        _OBJECTV2_ctor_eval+="
            eval \"__${_OBJECTV2_struct_name}_\${!id}_${valname}=\${params[$((ctor_id++))]}\""
    done
}

function objectv2::_option::string()
{
    local valname="$(arguments::get_val)"
    _OBJECTV2_cons_eval+="
        function ${_OBJECTV2_struct_name}::${valname}()
        {
            local name=\"__${_OBJECTV2_struct_name}_\${!1}_${valname}\"
            if [ \$# == 1 ]; then
                echo \${!name}
            elif [ \$# == 2 ]; then
                eval \"\${name}=\$2\"
            else
                assert_fail \"无法处理参数: \$*\"
            fi
        }
    "
    _OBJECTV2_dtor_eval+="
            eval \"unset __${_OBJECTV2_struct_name}_\${!id}_${valname}\""
    _OBJECTV2_dest_eval+="
            unset -f ${_OBJECTV2_struct_name}::${valname}"
}

function objectv2::_option::array()
{
    local valname="$(arguments::get_val)"
    _OBJECTV2_cons_eval+="
        function ${_OBJECTV2_struct_name}::${valname}()
        {
            local name=\"__${_OBJECTV2_struct_name}_\${!1}_${valname}\"
            if [ \$# == 2 ]; then
                if [ \$2 == len ]; then
                    eval \"echo \\\${#\$name[*]}\"
                else
                    eval \"echo \\\${\$name[\$2]}\"
                fi
            elif [ \$# == 3 ]; then
                if [ \$2 == push ]; then
                    eval \"\$name+=(\\\"\$3\\\")\"
                elif [ \$2 == erase ]; then
                    local object__option_array_swap=()
                    local object__option_array_len=0
                    eval \"object__option_array_len=\\\${#\$name[*]}\"
                    for (( object__option_array_i = 0; object__option_array_i < \$object__option_array_len; object__option_array_i++ )); do
                        local object__option_array_value
                        eval \"object__option_array_value=\\\"\\\${\$name[\\\$object__option_array_i]}\\\"\"
                        if [ \$object__option_array_value == \$3 ]; then
                            continue
                        fi
                        object__option_array_swap+=(\"\$object__option_array_value\")
                    done
                    eval \"\$name=(\${object__option_array_swap[*]})\"
                else
                    assert_fail \"没有该操作: \$*\"
                fi
            else
                assert_fail \"无法处理参数: \$*\"
            fi
        }
    "
    _OBJECTV2_ctor_eval+="
            eval \"__${_OBJECTV2_struct_name}_\${!id}_${valname}=()\""
    _OBJECTV2_dtor_eval+="
            eval \"unset __${_OBJECTV2_struct_name}_\${!id}_${valname}\""
    _OBJECTV2_dest_eval+="
            unset -f ${_OBJECTV2_struct_name}::${valname}"
}

function objectv2::define_struct()
{
    _OBJECTV2_struct_name="$1"
    shift

    arguments $*
    while ! arguments::is_end; do
        call "objectv2::_option::$(arguments::get_key)"
        arguments::next
    done
    arguments::destroy

    eval "
        _${_OBJECTV2_struct_name}_nextid=0
        _${_OBJECTV2_struct_name}_refs=0
        function ${_OBJECTV2_struct_name}()
        {
            local define=\"\$*\"
            local symbol=\"\${define//\{*/}\"
            define=\"\${define:\$((\${#symbol} + 1)):-1}\"

            local params=()
            while (( \${#define} > 0 )); do
                local param=\"\${define//,*/}\"
                params+=(\"\${define//,*/}\")
                define=\"\${define:\$((\${#param} + 2))}\"
            done

            local id=_${_OBJECTV2_struct_name}_nextid $_OBJECTV2_ctor_eval
            (( \$symbol = _${_OBJECTV2_struct_name}_nextid++ ))
            (( _${_OBJECTV2_struct_name}_refs++ ))
            if declare -f ${_OBJECTV2_struct_name}::${_OBJECTV2_struct_name} 1>/dev/null; then
                ${_OBJECTV2_struct_name}::${_OBJECTV2_struct_name} \$symbol
            fi
        }

        function ${_OBJECTV2_struct_name}::destroy()
        {
            local id=\${!1} $_OBJECTV2_dtor_eval
            if declare -f ${_OBJECTV2_struct_name}::~${_OBJECTV2_struct_name} 1>/dev/null; then
                ${_OBJECTV2_struct_name}::~${_OBJECTV2_struct_name} \$symbol
            fi

            (( _${_OBJECTV2_struct_name}_refs-- ))
            if (( _${_OBJECTV2_struct_name}_refs != 0 )); then
                return 0
            fi
            $_OBJECTV2_dest_eval
        }
        $_OBJECTV2_cons_eval
    "
    unset _OBJECTV2_struct_name
    unset _OBJECTV2_cons_eval
    unset _OBJECTV2_ctor_eval
    unset _OBJECTV2_dtor_eval
    unset _OBJECTV2_dest_eval
}

function objectv2::test1()
{
    objectv2::define_struct OBJ "
        --cargs=url,name
        --string=url
        --string=name
        --array=actions
    "

    OBJ obj1{www.baidu.com, baidu}

    # cargs
    test::run_cmd OBJ::url obj1
    test::should_be www.baidu.com
    test::run_cmd OBJ::name obj1
    test::should_be baidu

    # string setter
    OBJ::url obj1 fanyi.youdao.com
    test::run_cmd OBJ::url obj1
    test::should_be fanyi.youdao.com

    # array operation
    test::run_cmd OBJ::actions obj1 len
    test::should_be 0

    ## push 123
    OBJ::actions obj1 push 123
    test::run_cmd OBJ::actions obj1 len
    test::should_be 1
    test::run_cmd OBJ::actions obj1 0
    test::should_be 123

    # other object
    OBJ obj2{www.baidu.com, baidu}
    test::run_cmd OBJ::name obj1
    test::should_be baidu
    test::run_cmd OBJ::url obj1
    test::should_be fanyi.youdao.com

    # other object destroy
    OBJ::destroy obj2
    test::run_cmd OBJ::name obj1
    test::should_be baidu
    test::run_cmd OBJ::url obj1
    test::should_be fanyi.youdao.com
    OBJ::actions obj1 push 456
    OBJ::actions obj1 push 789
    test::run_cmd OBJ::actions obj1 len
    test::should_be 3
    test::run_cmd OBJ::actions obj1 0
    test::should_be 123
    test::run_cmd OBJ::actions obj1 1
    test::should_be 456
    test::run_cmd OBJ::actions obj1 2
    test::should_be 789
}
test::register_test objectv2::test1

function objectv2::test2()
{
    objectv2::define_struct OBJ "
        --array=actions
    "

    OBJ obj{www.baidu.com, baidu}
    OBJ::actions obj push 123
    OBJ::actions obj push 456
    OBJ::actions obj push 789
    test::run_cmd OBJ::actions obj len
    test::should_be 3
    test::run_cmd OBJ::actions obj 1
    test::should_be 456

    # erase
    OBJ::actions obj erase 456
    test::run_cmd OBJ::actions obj len
    test::should_be 2
    test::run_cmd OBJ::actions obj 1
    test::should_be 789

    OBJ::destroy obj
}
test::register_test objectv2::test2
